package cn.edu.zut.sunday.blimp.service.impl;

import cn.edu.zut.sunday.blimp.dao.DatasetNameRepository;
import cn.edu.zut.sunday.blimp.domain.DatasetName;
import cn.edu.zut.sunday.blimp.domain.ExampleInfo;
import cn.edu.zut.sunday.blimp.service.ImportDataService;
import cn.edu.zut.sunday.constant.BlimpDatasetInfo;
import cn.edu.zut.sunday.utils.MD5Utils;
import cn.edu.zut.sunday.utils.ReloadFileUtils;
import com.google.common.collect.Lists;
import com.google.gson.Gson;
import java.io.File;
import java.io.IOException;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Slf4j
@Service
public class ImportDataServiceImpl implements ImportDataService {

    private DatasetNameRepository datasetNameRepository;

    private final Gson gson = new Gson();

    @Autowired
    public void setDatasetNameRepository(DatasetNameRepository datasetNameRepository) {
        this.datasetNameRepository = datasetNameRepository;
    }

    /**
     * 用来读取数据集文件，加载该文件数据（json格式）写入到数据库中
     *
     * @return 是否操作成功
     */
    @Override
    public boolean initDatabase() {
        try {
            List<File> files = ReloadFileUtils.getFiles(BlimpDatasetInfo.DATASET_FILE_PATH);
            // 没有文件则直接返回，不执行加载数据操作
            if (files.size() == 0) {
                return true;
            }
            // 获取需要更新的文件
            files = updateFiles(files);
            if (files.isEmpty()) {
                // 不需要更新
                return true;
            }
            // 封装Dataset数据格式
            Map<File, DatasetName> fileDatasetNameMap = dealDataset(files);
            // 插入数据到数据库
            fileDatasetNameMap.forEach((file, entity) -> {

                // 获取相关的样本信息列表
                final List<ExampleInfo> exampleInfos = dealExampleInfo(entity, file);
                // 设置一对多
                entity.setExampleInfos(exampleInfos);

                final DatasetName save = datasetNameRepository.save(entity);
                log.info("Dataset Name: {}-[id: {}]-[count: {}]insert success.",
                  save.getSetName(), save.getId(), save.getDataCount());
            });
            return true;
        } catch (Exception e) {
            log.error("read dataset failed. path is: [ {} ], error: {}",
              BlimpDatasetInfo.DATASET_FILE_PATH, e.getMessage());
        }

        return false;
    }

    @Override
    public String datasetName() {
        return BlimpDatasetInfo.DATASET_NAME;
    }

    /**
     * 判断当前文件列表中是否存在需要更新的文件
     *
     * @param files 文件列表
     * @return 需要更新的文件列表
     */
    private List<File> updateFiles(List<File> files) {
        final List<DatasetName> allDataSet = Lists.newArrayList(datasetNameRepository.findAll());
        final Map<String, String> dbName2HashcodeMap = allDataSet.stream()
          .collect(Collectors.toMap(DatasetName::getSetName, DatasetName::getMd5));
        final Map<String, Integer> dbName2Id = allDataSet.stream()
          .collect(Collectors.toMap(DatasetName::getSetName, DatasetName::getId));
        // 存储需要更新的文件
        List<File> update = Lists.newArrayList();
        files.stream()
          .filter(file -> file.getName().endsWith(BlimpDatasetInfo.DATASET_FILE_SUFFIX))
          .forEach(file -> {
              final String md5 = dbName2HashcodeMap.getOrDefault(file.getName(), "none");
              // 如果该文件没有md5，则表示该文件未加载到数据库
              if ("none".equals(md5)) {
                  update.add(file);
              } else {
                  // md5不一样，说明文件内容做了更改操作，删除数据库内容文件，重新从文件加载数据
                  if (!md5.equals(MD5Utils.getMD5(file))) {
                      update.add(file);
                      // 删除更新的文件相关的存储
                      final Integer id = dbName2Id.get(file.getName());
                      // 这是做的时数据库级联删除，默认把相关的内容删除
                      datasetNameRepository.deleteById(id);
                  }
              }
          });
        return update;
    }

    /**
     * 根据文件信息创建对应的数据库实体对象，并执行插入操作
     *
     * @param entity 数据库实体
     * @param file 文件对象
     * @return 该数据集文件中的样本信息对象列表
     */
    private List<ExampleInfo> dealExampleInfo(DatasetName entity, File file) {
        if (file == null) {return Lists.newArrayList();}
        try {
            final List<String> allLines = ReloadFileUtils.readFileAllLines(file);
            // 读取文件内容，获取ExampleInfo实体列表
            return allLines.stream().map(line -> {
                // 从json数据格式填充实体
                final ExampleInfo exampleInfo = gson.fromJson(line, ExampleInfo.class);
                exampleInfo.setDateCreated(new Date());
                exampleInfo.setDateModified(new Date());
                // 设置多对一的实体
                exampleInfo.setDatasetName(entity);
                return exampleInfo;
            }).collect(Collectors.toList());
        } catch (IOException e) {
            log.error("Error: {}", e.getMessage());
        }
        return Lists.newArrayList();
    }

    /**
     * 封装文件信息到实体对象
     *
     * @param files 文件列表
     * @return 文件和实体对象组成的对应map
     */
    private Map<File, DatasetName> dealDataset(List<File> files) {
        return files.stream()
          .collect(Collectors.toMap(file -> file, file -> {
              final DatasetName datasetName = new DatasetName();
              final int         dotIdx      = file.getName().lastIndexOf(".");
              datasetName.setSetName(file.getName().substring(0, dotIdx));
              datasetName.setSuffix(file.getName().substring(dotIdx + 1));
              datasetName.setDataCount(ReloadFileUtils.lines(file));
              datasetName.setMd5(MD5Utils.getMD5(file));
              datasetName.setDateCreated(new Date());
              datasetName.setDateModified(new Date());
              return datasetName;
          }));
    }
}
